﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Tizer.Utilities
{
    /// <summary>
    /// Генератор паролей
    /// </summary>
    public class PasswordGenerator
    {
        /// <summary>
        /// Криптографический генератор случайных чисел
        /// </summary>
        private readonly RNGCryptoServiceProvider _rngCryptoServiceProvider = new RNGCryptoServiceProvider();

        #region Public fields

        /// <summary>
        /// Минимальная длина пароля
        /// </summary>
        public int Minimum { get; set; }

        /// <summary>
        /// Максимальная длина пароля
        /// </summary>
        public int Maximum { get; set; }

        /// <summary>
        /// Разрешить/запретить повторение символов в пароле
        /// </summary>
        public bool RepeatCharacters { get; set; }

        /// <summary>
        /// Использовать цифры
        /// </summary>
        public bool UseNumbers { get; set; }

        /// <summary>
        /// Использовать специальные символы
        /// </summary>
        public bool UseSpecialSymbols { get; set; }

        /// <summary>
        /// Использовать русский алфавит
        /// </summary>
        public bool UseRussianAlphabetical { get; set; }

        /// <summary>
        /// Кодировка пароля
        /// </summary>
        public Encoding PasswordEncode { get; set; }

        #endregion

        #region Private fields

        /// <summary>
        /// Английский алфавит
        /// </summary>
        private char[] EnglishAlphabet { get; set; }

        /// <summary>
        /// Русский алфавит
        /// </summary>
        private char[] RussianAlphabet { get; set; }

        /// <summary>
        /// Массив чисел
        /// </summary>
        private char[] Numbers { get; set; }

        /// <summary>
        /// Специальные символы
        /// </summary>
        private char[] SpecialSymbols { get; set; }

        /// <summary>
        /// Массив исходных данных для генерации пароля
        /// </summary>
        private char[] PasswordCharArray { get; set; }

        #endregion

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <remarks>Использует значения полей по-умолчанию если не заданы значения</remarks>
        public PasswordGenerator()
        {
            this.Minimum = 8;
            this.Maximum = 8;
            this.RepeatCharacters = false;
            this.UseSpecialSymbols = false;
            this.UseNumbers = false;
            this.UseRussianAlphabetical = false;
            Encoding encoding = Encoding.UTF8;
        }

        /// <summary>
        /// Генерация пароля
        /// </summary>
        /// <returns>Пароль</returns>
        public string GeneratePassword()
        {
            string generatedPassword = String.Empty;

            CreatePasswordCharArray();

            int passwordLength = GetCryptographicRandomNumber(this.Minimum, this.Maximum);
            var passwordBuffer = new StringBuilder { Capacity = this.Maximum };
            char nextCharacter, lastCharacter;
            lastCharacter = nextCharacter = '\n';
            for (int i = 0; i < passwordLength; i++)
            {
                nextCharacter = GetRandomCharacter();
                if (false == this.RepeatCharacters)
                {
                    string temp = passwordBuffer.ToString();
                    int duplicateIndex = temp.IndexOf(nextCharacter);
                    while (-1 != duplicateIndex)
                    {
                        nextCharacter = GetRandomCharacter();
                        duplicateIndex = temp.IndexOf(nextCharacter);
                    }
                }
                passwordBuffer.Append(nextCharacter);
                lastCharacter = nextCharacter;
            }
            generatedPassword = passwordBuffer.ToString();

            return generatedPassword;
        }


        /// <summary>
        /// Получение случайного символа в массиве исходных данных
        /// </summary>
        /// <returns>Случайный символ</returns>
        private char GetRandomCharacter()
        {
            int upperBound = PasswordCharArray.GetUpperBound(0);
            int randomCharPosition = GetCryptographicRandomNumber(PasswordCharArray.GetLowerBound(0), upperBound);
            char randomChar = PasswordCharArray[randomCharPosition];
            return randomChar;
        }


        /// <summary>
        /// Генерация случайного числа
        /// </summary>
        /// <param name="lowerLimit">Нижний лимит</param>
        /// <param name="upperLimit">Верхний лимит</param>
        /// <returns>Число</returns>
        protected int GetCryptographicRandomNumber(int lowerLimit, int upperLimit)
        {
            uint urndnum;
            var rndnum = new Byte[4];
            if (lowerLimit >= upperLimit - 1)
            {
                return lowerLimit;
            }
            uint xcludeRndBase = (uint.MaxValue - (uint.MaxValue % (uint)(upperLimit - lowerLimit)));
            do
            {
                _rngCryptoServiceProvider.GetBytes(rndnum);
                urndnum = BitConverter.ToUInt32(rndnum, 0);
            } while (urndnum >= xcludeRndBase);
            return (int)(urndnum % (upperLimit - lowerLimit)) + lowerLimit;
        }


        /// <summary>
        /// Проверка длины пароля
        /// </summary>
        private void PasswordLengthValidator()
        {
            if (this.Minimum < 1)
            {
                this.Minimum = 1;
            }
            if (this.Maximum > 50)
            {
                this.Maximum = 50;
            }
        }

        /// <summary>
        /// Получение массива исходных данных для генерации пароля
        /// </summary>
        private void CreatePasswordCharArray()
        {
            byte[] bytesAlphabetEnglish = PasswordEncode.GetBytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
            EnglishAlphabet = PasswordEncode.GetChars(bytesAlphabetEnglish);

            byte[] bytesAlphabetRussian = PasswordEncode.GetBytes("абвгдежзиклмнопрстуфхцчшщъыьэюяАБВГДЕЖЗИКЛМНОПРСТУФХЦЧШЩЫЬЭЮЯ");
            RussianAlphabet = PasswordEncode.GetChars(bytesAlphabetRussian);

            byte[] bytesNumbers = PasswordEncode.GetBytes("0123456789");
            Numbers = PasswordEncode.GetChars(bytesNumbers);

            byte[] bytesSpecialSymbols = PasswordEncode.GetBytes(".,!?()$#%[]}{/");
            SpecialSymbols = PasswordEncode.GetChars(bytesSpecialSymbols);

            // Форимруем исходный массив в зависимости от настроек генератора

            PasswordCharArray = EnglishAlphabet;
            if (this.UseNumbers)
            {
                PasswordCharArray = PasswordCharArray.Concat(this.Numbers).ToArray();
            }
            if (this.UseRussianAlphabetical)
            {
                PasswordCharArray = PasswordCharArray.Concat(this.RussianAlphabet).ToArray();
            }
            if (this.UseSpecialSymbols)
            {
                PasswordCharArray = PasswordCharArray.Concat(this.SpecialSymbols).ToArray();
            }
        }
    }

}
